/**
 * Copyright [2012-2013] [Guðmundur Björn Birkisson]
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. Copyright [2012-2013] [Guðmundur Björn Birkisson]
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * ---------------------------------------------------------------------------
 * 
 * Class name:  Renderer
 * 
 * Description of class:
 * 
 * A buffered renderer that uses sprites to represent the Karel World.
 * 
 */

package karel.graphics;

import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import karel.gamelogic.GameContainer;
import karel.gamelogic.world.KarelMap;
import karel.gamelogic.world.KarelRobot;
import karel.gamelogic.world.Tile;
import karel.util.FontLoader;

public class BufferedRenderer extends JPanel implements Renderer {

    private BufferedImage mapIMG;
    private BufferedImage rockImage;
    private BufferedImage rockNumImage;
    private BufferedImage scene;
    private Graphics2D sceneGraphics;
    private GameContainer game;
    private int tileSize;
    private int offsetx;
    private int offsety;
    private Image robotImage;
    private Image rockImage1;
    private Image rockImage2;
    private Image rockImage3;
    private Font rendererFont;

    public BufferedRenderer() {
        this.addComponentListener(this);
        rendererFont = FontLoader.getDefaultFont(Font.BOLD, 20);
        try {
            robotImage = ImageIO.read(getClass().getResource("/images/robot.png"));
            rockImage1 = ImageIO.read(getClass().getResource("/images/rock1.png"));
            rockImage2 = ImageIO.read(getClass().getResource("/images/rock2.png"));
            rockImage3 = ImageIO.read(getClass().getResource("/images/rock3.png"));
        } catch (IOException ex) {
            Logger.getLogger(BufferedRenderer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    // create a hardware accelerated image
    private BufferedImage create(final int width, final int height, final boolean alpha) {
        return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(width, height, alpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE);
    }

    public void calcTileSizeAndOffset() {
        if (game == null) {
            return;
        }
        tileSize = Math.min(getSize().width / game.map.getWidth(), getSize().height / game.map.getHeight());
        offsetx = (getSize().width - tileSize * game.map.getWidth()) / 2;
        offsety = (getSize().height - tileSize * game.map.getHeight()) / 2;
    }

    @Override
    public void setGameContainer(GameContainer game) {
        this.game = game;
    }

    @Override
    public void paintComponent(Graphics g) {
        if (game == null) {
            g.clearRect(0, 0, getSize().width, getSize().height);
            return;
        }

        if (mapIMG == null) {
            mapIMG = create(getSize().width, getSize().height, false);
            renderMap((Graphics2D) mapIMG.getGraphics(), game.map, tileSize);
        }

        rockImage = create(getSize().width, getSize().height, true);
        rockNumImage = create(getSize().width, getSize().height, true);
        renderRocks((Graphics2D) rockImage.getGraphics(),(Graphics2D) rockNumImage.getGraphics(), game.map, tileSize);

        AffineTransform origTransform = sceneGraphics.getTransform();
        sceneGraphics.translate(offsetx, offsety);
        sceneGraphics.drawImage(mapIMG, 0, 0, getSize().width, getSize().height, null);
        sceneGraphics.drawImage(rockImage, 0, 0, getSize().width, getSize().height, null);
        AffineTransform tempTrans = sceneGraphics.getTransform();
        renderRobot(sceneGraphics, game.robot, tileSize);
        sceneGraphics.setTransform(tempTrans);
        sceneGraphics.drawImage(rockNumImage, 0, 0, getSize().width, getSize().height, null);
        sceneGraphics.setTransform(origTransform);
        
        g.drawImage(scene, 0, 0, getSize().width, getSize().height, null);
    }

    @Override
    public void repaintScene() {
        repaint();
    }

    @Override
    public void componentResized(ComponentEvent e) {
        if (getSize().width == 0 || getSize().height == 0) {
            return;
        }
        calcTileSizeAndOffset();
        mapIMG = null;
        rockImage = null;
        scene = create(getSize().width, getSize().height, false);
        sceneGraphics = (Graphics2D) scene.getGraphics();
        repaintScene();
    }

    @Override
    public void componentMoved(ComponentEvent e) {
        //Do Nothing
    }

    @Override
    public void componentShown(ComponentEvent e) {
        //Do Nothing
    }

    @Override
    public void componentHidden(ComponentEvent e) {
        //Do Nothing
    }

    private void renderMap(Graphics2D g, KarelMap map, int tileSize) {
        for (int x = 0; x < map.getWidth(); x++) {
            for (int y = 0; y < map.getHeight(); y++) {

                g.setColor(Tile.getColor(Tile.Type.CLEAR));
                if (map.isType(x, y, Tile.Type.BLOCKED)) {
                    g.setColor(Tile.getColor(Tile.Type.BLOCKED));
                } else if (map.isType(x, y, Tile.Type.EXIT)) {
                    g.setColor(Tile.getColor(Tile.Type.EXIT));
                }

                // draw the rectangle with a dark outline
                g.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
                g.setColor(g.getColor().darker());
                g.drawRect(x * tileSize, y * tileSize, tileSize, tileSize);
            }
        }

    }

    private void renderRocks(Graphics2D gRock,Graphics2D gNum, KarelMap map, int tileSize) {
        gNum.setFont(rendererFont);
        for (int x = 0; x < map.getWidth(); x++) {
            for (int y = 0; y < map.getHeight(); y++) {
                if (map.isType(x, y, Tile.Type.ROCK)) {
                    int rocks = map.getNumberOfRocks(x, y);
                    switch (rocks) {
                        case 1:
                            gRock.drawImage(rockImage1, x * tileSize, y * tileSize, tileSize, tileSize, null);
                            break;
                        case 2:
                            gRock.drawImage(rockImage2, x * tileSize, y * tileSize, tileSize, tileSize, null);
                            break;
                        case 3:
                            gRock.drawImage(rockImage3, x * tileSize, y * tileSize, tileSize, tileSize, null);
                            break;
                        default:
                            String str = Integer.toString(map.getNumberOfRocks(x, y));
                            gRock.drawImage(rockImage3, x * tileSize, y * tileSize, tileSize, tileSize, null);
                            gNum.setColor(new Color(0.0f, 0.0f, 0.3f));
                            gNum.drawString(str, (x + 1) * tileSize - gNum.getFontMetrics(rendererFont).stringWidth(str) - 5, (y + 1) * tileSize - 5);
                    }
                }
            }
        }
    }

    private void renderRobot(Graphics2D g, KarelRobot robot, int tileSize) {
        int translationx = (int) (tileSize * robot.postition.getX()) + tileSize / 2 + 1;
        int translationy = (int) (tileSize * robot.postition.getY()) + tileSize / 2 + 1;
        double rotation = robot.direction;

        g.translate(translationx, translationy);
        g.rotate(rotation);
        g.drawImage(robotImage, -tileSize / 2, -tileSize / 2, tileSize, tileSize, null);
    }
}
